use inkwell::IntPredicate;
use inkwell::basic_block::BasicBlock;
use inkwell::builder::Builder;
use inkwell::types::BasicMetadataTypeEnum;
use inkwell::values::BasicMetadataValueEnum;
use inkwell::values::CallSiteValue;
use inkwell::values::FunctionValue;
use inkwell::values::GlobalValue;
use inkwell::values::IntValue;
use inkwell::values::PointerValue;
use inkwell::{context::Context, module::Module};
use pest::Parser;
use pest::iterators::Pair;
use pest_derive::Parser;
use std::collections::HashMap;

struct TableManager<'ctx> {
    tables: Vec<Table<'ctx>>,
}

impl<'ctx> TableManager<'ctx> {
    fn new() -> Self {
        Self { tables: Vec::new() }
    }

    fn is_global(&self) -> bool {
        self.tables.is_empty()
    }

    fn push_scope(&mut self) {
        self.tables.push(Table::new());
    }

    fn pop_scope(&mut self) {
        self.tables.pop().unwrap();
    }

    fn push_symbol(&mut self, name: &str, p: PointerValue<'ctx>) {
        self.tables.last_mut().unwrap().push_symbol(name, p);
    }

    fn find(&self, name: &str) -> Option<PointerValue<'ctx>> {
        for table in self.tables.iter().rev() {
            if let Some(pointer) = table.find(name) {
                return Some(pointer);
            }
        }
        None
    }
}

struct Table<'ctx> {
    symbols: Symbols<'ctx>,
}

impl<'ctx> Table<'ctx> {
    fn new() -> Self {
        Self {
            symbols: HashMap::new(),
        }
    }

    fn push_symbol(&mut self, name: &str, p: PointerValue<'ctx>) {
        self.symbols.insert(name.to_string(), p);
    }

    fn find(&self, name: &str) -> Option<PointerValue<'ctx>> {
        self.symbols.get(name).copied()
    }
}

type Symbols<'ctx> = HashMap<String, PointerValue<'ctx>>;

#[derive(Parser)]
#[grammar = "parser.pest"]
pub struct SysYParser;

struct IRGenerator<'ctx> {
    pub context: &'ctx Context,
    pub module: Module<'ctx>,
    pub builder: Builder<'ctx>,
    table_manager: TableManager<'ctx>,
    while_blocks: Vec<(BasicBlock<'ctx>, BasicBlock<'ctx>)>,
    current_block_terminated: bool,
}

impl<'ctx> IRGenerator<'ctx> {
    pub fn new(context: &'ctx Context, module_name: &str) -> Self {
        let module = context.create_module(module_name);
        let builder = context.create_builder();
        let table_manager = TableManager::new();
        let while_blocks = Vec::new();
        let current_block_terminated = false;

        IRGenerator {
            context,
            module,
            builder,
            table_manager,
            while_blocks,
            current_block_terminated,
        }
    }

    fn get_const_val(&self, num: i32) -> IntValue<'ctx> {
        let i32_type = self.context.i32_type();
        i32_type.const_int(num as u32 as u64, true)
    }

    fn add_global(&mut self, name: &str) -> GlobalValue<'ctx> {
        let i32_type = self.context.i32_type();
        self.module.add_global(i32_type, None, name)
    }

    fn add_local(&mut self, name: &str) -> PointerValue<'ctx> {
        let i32_type = self.context.i32_type();
        let p = self.builder.build_alloca(i32_type, name).unwrap();
        self.table_manager.push_symbol(name, p);
        p
    }

    fn find_symbol(&self, name: &str) -> PointerValue<'ctx> {
        if let Some(pointer) = self.table_manager.find(name) {
            return pointer;
        }
        self.module.get_global(name).unwrap().as_pointer_value()
    }

    fn current_function(&self) -> FunctionValue<'ctx> {
        self.module.get_last_function().unwrap()
    }

    fn store(&self, p: PointerValue<'ctx>, val: IntValue<'ctx>) {
        self.builder.build_store(p, val).unwrap();
    }

    fn load(&self, p: PointerValue<'ctx>) -> IntValue<'ctx> {
        self.builder.build_load(p, "tmp_").unwrap().into_int_value()
    }

    fn add(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder.build_int_add(val1, val2, "tmp_").unwrap()
    }

    fn sub(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder.build_int_sub(val1, val2, "tmp_").unwrap()
    }

    fn mul(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder.build_int_mul(val1, val2, "tmp_").unwrap()
    }

    fn div(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder
            .build_int_signed_div(val1, val2, "tmp_")
            .unwrap()
    }

    fn rem(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder
            .build_int_signed_rem(val1, val2, "tmp_")
            .unwrap()
    }

    fn call(&self, func_name: &str, r_params: Vec<IntValue<'ctx>>) -> CallSiteValue<'ctx> {
        let function = self.module.get_function(func_name).unwrap();
        let params: Vec<BasicMetadataValueEnum> =
            r_params.into_iter().map(|int_val| int_val.into()).collect();
        self.builder
            .build_call(function, &params, "returnValue")
            .unwrap()
    }

    fn cmp_eq(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder
            .build_int_compare(IntPredicate::EQ, val1, val2, "cmp_")
            .unwrap()
    }

    fn cmp_ne(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder
            .build_int_compare(IntPredicate::NE, val1, val2, "cmp_")
            .unwrap()
    }

    fn cmp_gt(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder
            .build_int_compare(IntPredicate::SGT, val1, val2, "cmp_")
            .unwrap()
    }

    fn cmp_ge(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder
            .build_int_compare(IntPredicate::SGE, val1, val2, "cmp_")
            .unwrap()
    }

    fn cmp_lt(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder
            .build_int_compare(IntPredicate::SLT, val1, val2, "cmp_")
            .unwrap()
    }

    fn cmp_le(&self, val1: IntValue<'ctx>, val2: IntValue<'ctx>) -> IntValue<'ctx> {
        self.builder
            .build_int_compare(IntPredicate::SLE, val1, val2, "cmp_")
            .unwrap()
    }

    fn i1_to_i32(&self, val: IntValue<'ctx>) -> IntValue<'ctx> {
        let i32_type = self.context.i32_type();
        self.builder
            .build_int_z_extend(val, i32_type, "extend_")
            .unwrap()
    }

    fn br(&self, block: BasicBlock<'ctx>) {
        self.builder.build_unconditional_branch(block).unwrap();
    }

    fn cond_br(
        &self,
        val: IntValue<'ctx>,
        true_block: BasicBlock<'ctx>,
        false_block: BasicBlock<'ctx>,
    ) {
        self.builder
            .build_conditional_branch(val, true_block, false_block)
            .unwrap();
    }

    fn push_while(&mut self, while_body: BasicBlock<'ctx>, while_next: BasicBlock<'ctx>) {
        self.while_blocks.push((while_body, while_next))
    }

    fn pop_while(&mut self) {
        self.while_blocks.pop().unwrap();
    }

    fn get_last_while(&self) -> (BasicBlock<'ctx>, BasicBlock<'ctx>) {
        *self.while_blocks.last().unwrap()
    }

    fn reverse_val(&self, val: IntValue<'ctx>) -> IntValue<'ctx> {
        let i32_type = self.context.i32_type();
        let negative_one = i32_type.const_int(u32::MAX as u64, true);
        self.builder.build_int_mul(val, negative_one, "").unwrap()
    }
    fn visit_program(&mut self, pair: Pair<Rule>) {
        for comp_unit in pair.into_inner() {
            match comp_unit.as_rule() {
                Rule::comp_unit => {
                    self.visit_comp_unit(comp_unit);
                }
                Rule::EOI => (),
                _ => unreachable!(),
            }
        }
    }

    fn visit_comp_unit(&mut self, pair: Pair<Rule>) {
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::func_def => {
                    self.visit_func_def(pair);
                }
                Rule::decl => self.visit_decl(pair),
                _ => unreachable!(),
            }
        }
    }

    fn visit_decl(&mut self, pair: Pair<Rule>) {
        let pair = pair.into_inner().next().unwrap();
        match pair.as_rule() {
            Rule::const_decl => self.visit_const_decl(pair),
            Rule::var_decl => self.visit_var_decl(pair),
            _ => unreachable!(),
        }
    }

    fn visit_const_decl(&mut self, pair: Pair<Rule>) {
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::r#const => (),
                Rule::b_type => (),
                Rule::const_def => self.visit_const_def(pair),
                Rule::comma => (),
                Rule::semicolon => (),
                _ => unreachable!(),
            }
        }
    }

    fn visit_const_def(&mut self, pair: Pair<Rule>) {
        let mut ident: Option<String> = None;
        let mut val: Option<ExpVal> = None;
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::ident => ident = Some(pair.as_str().into()),
                Rule::assign => (),
                Rule::const_init_val => val = Some(self.visit_const_init_val(pair)),
                _ => unreachable!(),
            }
        }
        let ident = ident.unwrap();
        let init_val = match val {
            Some(exp_val) => match exp_val {
                ExpVal::Num(num) => Some(self.get_const_val(num)),
                ExpVal::Val(int_value) => Some(int_value),
            },
            None => None,
        };
        if self.table_manager.is_global() {
            let global = self.add_global(&ident);
            if let Some(init_val) = init_val {
                global.set_initializer(&init_val);
            }
        } else {
            let p = { self.add_local(&ident) };
            if let Some(init_val) = init_val {
                self.store(p, init_val);
            }
        }
    }

    fn visit_const_init_val(&mut self, pair: Pair<Rule>) -> ExpVal<'ctx> {
        let pair = pair.into_inner().next().unwrap();
        match pair.as_rule() {
            Rule::const_exp => self.visit_const_exp(pair),
            _ => unreachable!(),
        }
    }

    fn visit_var_decl(&mut self, pair: Pair<Rule>) {
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::b_type => (),
                Rule::var_def => self.visit_var_def(pair),
                Rule::comma => (),
                Rule::semicolon => (),
                _ => unreachable!(),
            }
        }
    }

    fn visit_var_def(&mut self, pair: Pair<Rule>) {
        let mut ident: Option<String> = None;
        let mut val: Option<ExpVal> = None;
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::ident => ident = Some(pair.as_str().into()),
                Rule::assign => (),
                Rule::init_val => val = Some(self.visit_init_val(pair)),
                _ => unreachable!(),
            }
        }
        let ident = ident.unwrap();
        let init_val = match val {
            Some(exp_val) => match exp_val {
                ExpVal::Num(num) => Some(self.get_const_val(num)),
                ExpVal::Val(int_value) => Some(int_value),
            },
            None => None,
        };
        if self.table_manager.is_global() {
            let global = self.add_global(&ident);
            if let Some(init_val) = init_val {
                global.set_initializer(&init_val);
            }
        } else {
            let p = self.add_local(&ident);
            if let Some(init_val) = init_val {
                self.store(p, init_val);
            }
        }
    }

    fn visit_init_val(&mut self, pair: Pair<Rule>) -> ExpVal<'ctx> {
        let pair = pair.into_inner().next().unwrap();
        match pair.as_rule() {
            Rule::exp => self.visit_exp(pair),
            _ => unreachable!(),
        }
    }

    fn visit_func_def(&mut self, pair: Pair<Rule>) {
        enum State {
            Int,
            Void,
        }
        let mut func_type: Option<State> = None;
        let mut func_name: Option<String> = None;
        let mut block: Option<Pair<Rule>> = None;
        let mut names: Vec<String> = Vec::new();
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::func_type => {
                    let pair = pair.into_inner().next().unwrap();
                    match pair.as_rule() {
                        Rule::void => func_type = Some(State::Void),
                        Rule::int => func_type = Some(State::Int),
                        _ => unreachable!(),
                    }
                }
                Rule::ident => func_name = Some(pair.as_str().into()),
                Rule::l_paren => (),
                Rule::r_paren => (),
                Rule::func_f_params => self.visit_func_f_params(pair, &mut names),
                Rule::block => {
                    block = Some(pair);
                }
                _ => unreachable!(),
            }
        }
        let func_type = func_type.unwrap();
        let func_name = func_name.unwrap();
        let block = block.unwrap();
        let i32_type = self.context.i32_type();
        let arg_types = vec![BasicMetadataTypeEnum::IntType(i32_type); names.len()];
        let fn_type = match func_type {
            State::Int => {
                let ty = self.context.i32_type();
                ty.fn_type(&arg_types, false)
            }
            State::Void => {
                let ty = self.context.void_type();
                ty.fn_type(&arg_types, false)
            }
        };
        let function = self.module.add_function(&func_name, fn_type, None);
        let entry_block = self
            .context
            .append_basic_block(function, &(func_name + "Entry"));
        self.builder.position_at_end(entry_block);
        self.table_manager.push_scope();
        for (i, name) in names.iter().enumerate() {
            let p = self.add_local(name);
            let arg = function.get_nth_param(i as u32).unwrap().into_int_value();
            self.store(p, arg);
        }
        self.visit_block(block);
        self.table_manager.pop_scope();
        // check all blocks
        let blocks = function.get_basic_blocks();
        for block in blocks {
            if block.get_terminator().is_none() {
                self.builder.position_at_end(block);
                match func_type {
                    State::Void => self.builder.build_return(None).unwrap(),
                    State::Int => self.builder.build_unreachable().unwrap(),
                };
            }
        }
    }

    fn visit_func_f_params(&mut self, pair: Pair<Rule>, names: &mut Vec<String>) {
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::func_f_param => names.push(self.visit_func_f_param(pair)),
                Rule::comma => (),
                _ => unreachable!(),
            }
        }
    }

    fn visit_func_f_param(&mut self, pair: Pair<Rule>) -> String {
        let mut ident: Option<String> = None;
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::b_type => (),
                Rule::ident => ident = Some(pair.as_str().into()),
                _ => unreachable!(),
            }
        }
        ident.unwrap()
    }

    fn visit_block(&mut self, pair: Pair<Rule>) {
        self.table_manager.push_scope();
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::l_brace => (),
                Rule::block_item => {
                    self.visit_block_item(pair);
                }
                Rule::r_brace => (),
                _ => unreachable!(),
            }
        }
        self.table_manager.pop_scope();
    }

    fn visit_block_item(&mut self, pair: Pair<Rule>) {
        let pair = pair.into_inner().next().unwrap();
        match pair.as_rule() {
            Rule::stmt => {
                self.visit_stmt(pair);
            }
            Rule::decl => self.visit_decl(pair),
            _ => unreachable!(),
        }
    }

    fn visit_stmt(&mut self, pair: Pair<Rule>) {
        enum State {
            If,
            While,
            Break,
            Continue,
            Return,
            LVal,
            Block,
        }
        let mut state: Option<State> = None;
        let mut exp: Option<Pair<Rule>> = None;
        let mut l_val: Option<Pair<Rule>> = None;
        let mut stmt: Vec<Pair<Rule>> = Vec::new();
        let mut cond: Option<Pair<Rule>> = None;
        let mut block: Option<Pair<Rule>> = None;
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::r#if => state = Some(State::If),
                Rule::l_paren => (),
                Rule::cond => cond = Some(pair),
                Rule::r_paren => (),
                Rule::stmt => stmt.push(pair),
                Rule::r#else => (),
                Rule::r#while => state = Some(State::While),
                Rule::r#break => state = Some(State::Break),
                Rule::r#continue => state = Some(State::Continue),
                Rule::r#return => state = Some(State::Return),
                Rule::exp => exp = Some(pair),
                Rule::semicolon => (),
                Rule::l_val => l_val = Some(pair),
                Rule::assign => state = Some(State::LVal),
                Rule::block => {
                    state = Some(State::Block);
                    block = Some(pair);
                }
                _ => unreachable!(),
            }
        }
        if let Some(state) = state {
            match state {
                State::If => {
                    self.current_block_terminated = true;
                    let cond = cond.unwrap();
                    let function = self.current_function();
                    let if_true = self.context.append_basic_block(function, "if_true_");
                    let if_false = self.context.append_basic_block(function, "if_false_");
                    let if_next = self.context.append_basic_block(function, "if_next_");
                    self.visit_cond(cond, if_true, if_false);
                    let (stmt_true, stmt_false) = {
                        if stmt.len() == 1 {
                            (stmt.pop().unwrap(), None)
                        } else {
                            let stmt_false = stmt.pop().unwrap();
                            (stmt.pop().unwrap(), Some(stmt_false))
                        }
                    };
                    self.builder.position_at_end(if_true);
                    self.visit_stmt(stmt_true);
                    self.br(if_next);
                    self.builder.position_at_end(if_false);
                    if let Some(stmt_false) = stmt_false {
                        self.visit_stmt(stmt_false);
                    }
                    self.br(if_next);
                    self.builder.position_at_end(if_next);
                    self.current_block_terminated = false;
                }
                State::While => {
                    self.current_block_terminated = true;
                    let cond = cond.unwrap();
                    let stmt = stmt.pop().unwrap();
                    let function = self.current_function();
                    let while_cond = self.context.append_basic_block(function, "while_cond_");
                    let while_body = self.context.append_basic_block(function, "while_body_");
                    let while_next = self.context.append_basic_block(function, "while_next_");
                    self.br(while_cond);
                    self.builder.position_at_end(while_cond);
                    self.visit_cond(cond, while_body, while_next);
                    self.builder.position_at_end(while_body);
                    self.push_while(while_body, while_next);
                    self.visit_stmt(stmt);
                    self.pop_while();
                    self.br(while_cond);
                    self.builder.position_at_end(while_next);
                    self.current_block_terminated = false;
                }
                State::Break => {
                    self.current_block_terminated = true;
                    let (_, while_next) = self.get_last_while();
                    self.br(while_next);
                }
                State::Continue => {
                    self.current_block_terminated = true;
                    let (while_body, _) = self.get_last_while();
                    self.br(while_body);
                }
                State::Return => {
                    self.current_block_terminated = true;
                    if let Some(exp) = exp {
                        let exp_val = self.visit_exp(exp);
                        let ret = match exp_val {
                            ExpVal::Num(num) => self.get_const_val(num),
                            ExpVal::Val(val) => val,
                        };
                        let _ = self.builder.build_return(Some(&ret));
                    } else {
                        let _ = self.builder.build_return(None);
                    }
                }
                State::LVal => {
                    let exp = exp.unwrap();
                    let exp_val = self.visit_exp(exp);
                    let l_val = l_val.unwrap();
                    let p = self.visit_l_val(l_val);
                    let val = match exp_val {
                        ExpVal::Num(num) => self.get_const_val(num),
                        ExpVal::Val(val) => val,
                    };
                    self.store(p, val);
                }
                State::Block => {
                    let block = block.unwrap();
                    self.visit_block(block);
                }
            }
        } else if let Some(exp) = exp {
            let _ = self.visit_exp(exp);
        }
    }

    fn visit_exp(&mut self, pair: Pair<Rule>) -> ExpVal<'ctx> {
        let pair = pair.into_inner().next().unwrap();
        match pair.as_rule() {
            Rule::add_exp => self.visit_add_exp(pair),
            _ => unreachable!(),
        }
    }

    fn visit_cond(
        &mut self,
        pair: Pair<Rule>,
        true_block: BasicBlock<'ctx>,
        false_block: BasicBlock<'ctx>,
    ) {
        let pair = pair.into_inner().next().unwrap();
        match pair.as_rule() {
            Rule::l_or_exp => self.visit_l_or_exp(pair, true_block, false_block),
            _ => unreachable!(),
        }
    }

    fn visit_add_exp(&mut self, pair: Pair<Rule>) -> ExpVal<'ctx> {
        enum Op {
            Plus,
            Minus,
        }
        let mut nums: Vec<ExpVal> = Vec::new();
        let mut ops: Vec<Op> = Vec::new();
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::mul_exp => nums.push(self.visit_mul_exp(pair)),
                Rule::plus => ops.push(Op::Plus),
                Rule::minus => ops.push(Op::Minus),
                _ => unreachable!(),
            }
        }
        nums.reverse();
        ops.reverse();
        while nums.len() > 1 {
            let num1 = nums.pop().unwrap();
            let num2 = nums.pop().unwrap();
            let op = ops.pop().unwrap();
            let res = match (num1, num2) {
                (ExpVal::Num(num1), ExpVal::Num(num2)) => match op {
                    Op::Plus => ExpVal::Num(num1 + num2),
                    Op::Minus => ExpVal::Num(num1 - num2),
                },
                (ExpVal::Num(num1), ExpVal::Val(val2)) => {
                    let val1 = self.get_const_val(num1);
                    match op {
                        Op::Plus => ExpVal::Val(self.add(val1, val2)),
                        Op::Minus => ExpVal::Val(self.sub(val1, val2)),
                    }
                }
                (ExpVal::Val(val1), ExpVal::Num(num2)) => {
                    let val2 = self.get_const_val(num2);
                    match op {
                        Op::Plus => ExpVal::Val(self.add(val1, val2)),
                        Op::Minus => ExpVal::Val(self.sub(val1, val2)),
                    }
                }
                (ExpVal::Val(val1), ExpVal::Val(val2)) => match op {
                    Op::Plus => ExpVal::Val(self.add(val1, val2)),
                    Op::Minus => ExpVal::Val(self.sub(val1, val2)),
                },
            };
            nums.push(res);
        }
        nums.pop().unwrap()
    }

    fn visit_mul_exp(&mut self, pair: Pair<Rule>) -> ExpVal<'ctx> {
        enum Op {
            Mul,
            Div,
            Mod,
        }
        let mut nums: Vec<ExpVal> = Vec::new();
        let mut ops: Vec<Op> = Vec::new();
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::unary_exp => nums.push(self.visit_unary_exp(pair)),
                Rule::mul => ops.push(Op::Mul),
                Rule::div => ops.push(Op::Div),
                Rule::r#mod => ops.push(Op::Mod),
                _ => unreachable!(),
            }
        }
        nums.reverse();
        nums.reverse();
        while nums.len() > 1 {
            let num1 = nums.pop().unwrap();
            let num2 = nums.pop().unwrap();
            let op = ops.pop().unwrap();
            let res = match (num1, num2) {
                (ExpVal::Num(num1), ExpVal::Num(num2)) => match op {
                    Op::Mul => ExpVal::Num(num1 * num2),
                    Op::Div => ExpVal::Num(num1 / num2),
                    Op::Mod => ExpVal::Num(num1 % num2),
                },
                (ExpVal::Num(num1), ExpVal::Val(val2)) => {
                    let val1 = self.get_const_val(num1);
                    match op {
                        Op::Mul => ExpVal::Val(self.mul(val1, val2)),
                        Op::Div => ExpVal::Val(self.div(val1, val2)),
                        Op::Mod => ExpVal::Val(self.rem(val1, val2)),
                    }
                }
                (ExpVal::Val(val1), ExpVal::Num(num2)) => {
                    let val2 = self.get_const_val(num2);
                    match op {
                        Op::Mul => ExpVal::Val(self.mul(val1, val2)),
                        Op::Div => ExpVal::Val(self.div(val1, val2)),
                        Op::Mod => ExpVal::Val(self.rem(val1, val2)),
                    }
                }
                (ExpVal::Val(val1), ExpVal::Val(val2)) => match op {
                    Op::Mul => ExpVal::Val(self.mul(val1, val2)),
                    Op::Div => ExpVal::Val(self.div(val1, val2)),
                    Op::Mod => ExpVal::Val(self.rem(val1, val2)),
                },
            };
            nums.push(res);
        }
        nums.pop().unwrap()
    }

    fn visit_unary_exp(&mut self, pair: Pair<Rule>) -> ExpVal<'ctx> {
        #[derive(Clone)]
        enum Op {
            Plus,
            Minus,
            Not,
        }
        let mut num: Option<ExpVal> = None;
        let mut op: Option<Op> = None;
        let mut params: Vec<ExpVal> = Vec::new();
        let mut ident: Option<String> = None;
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::ident => ident = Some(pair.as_str().into()),
                Rule::l_paren => (),
                Rule::func_r_params => self.visit_func_r_params(pair, &mut params),
                Rule::r_paren => (),
                Rule::primary_exp => num = Some(self.visit_primary_exp(pair)),
                Rule::unary_op => {
                    let pair = pair.into_inner().next().unwrap();
                    op = Some(match pair.as_rule() {
                        Rule::plus => Op::Plus,
                        Rule::minus => Op::Minus,
                        Rule::not => Op::Not,
                        _ => unreachable!(),
                    });
                }
                Rule::unary_exp => {
                    let op = op.clone().unwrap();
                    let res = self.visit_unary_exp(pair);
                    num = Some(match res {
                        ExpVal::Num(num) => match op {
                            Op::Plus => ExpVal::Num(num),
                            Op::Minus => ExpVal::Num(-num),
                            Op::Not => ExpVal::Num(if num == 0 { 1 } else { 0 }),
                        },
                        ExpVal::Val(val) => match op {
                            Op::Plus => ExpVal::Val(val),
                            Op::Minus => ExpVal::Val(self.reverse_val(val)),
                            Op::Not => {
                                let zero = self.get_const_val(0);
                                let res = self.cmp_eq(zero, val);
                                let res = self.i1_to_i32(res);
                                ExpVal::Val(res)
                            }
                        },
                    });
                }
                _ => unreachable!(),
            }
        }
        if let Some(ident) = ident {
            // call
            let params: Vec<IntValue> = params
                .into_iter()
                .map(|exp_val| match exp_val {
                    ExpVal::Num(num) => self.get_const_val(num),
                    ExpVal::Val(val) => val,
                })
                .collect();
            let call_val = self.call(&ident, params);
            match call_val.try_as_basic_value().left() {
                Some(basic_val) => num = Some(ExpVal::Val(basic_val.into_int_value())),
                None => num = Some(ExpVal::Num(0)),
            }
        }
        num.unwrap()
    }

    fn visit_func_r_params(&mut self, pair: Pair<Rule>, params: &mut Vec<ExpVal<'ctx>>) {
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::exp => params.push(self.visit_exp(pair)),
                Rule::comma => (),
                _ => unreachable!(),
            }
        }
    }

    fn visit_l_val(&mut self, pair: Pair<Rule>) -> PointerValue<'ctx> {
        let mut ident: Option<String> = None;
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::ident => ident = Some(pair.as_str().into()),
                _ => unreachable!(),
            }
        }
        let ident = ident.unwrap();
        self.find_symbol(&ident)
    }

    fn visit_primary_exp(&mut self, pair: Pair<Rule>) -> ExpVal<'ctx> {
        let mut num: Option<ExpVal> = None;
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::l_paren => (),
                Rule::exp => num = Some(self.visit_exp(pair)),
                Rule::r_paren => (),
                Rule::l_val => {
                    let p = self.visit_l_val(pair);
                    num = Some(ExpVal::Val(self.load(p)));
                }
                Rule::number => num = Some(ExpVal::Num(parse_int(pair.as_str()).unwrap())),
                _ => unreachable!(),
            }
        }
        num.unwrap()
    }

    fn visit_rel_exp(&mut self, pair: Pair<Rule>) -> IntValue<'ctx> {
        enum Op {
            Le,
            Ge,
            Lt,
            Gt,
        }
        let mut exps: Vec<IntValue> = Vec::new();
        let mut ops: Vec<Op> = Vec::new();
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::add_exp => exps.push(match self.visit_add_exp(pair) {
                    ExpVal::Num(num) => self.get_const_val(num),
                    ExpVal::Val(val) => val,
                }),
                Rule::le => ops.push(Op::Le),
                Rule::ge => ops.push(Op::Ge),
                Rule::lt => ops.push(Op::Lt),
                Rule::gt => ops.push(Op::Gt),
                _ => unreachable!(),
            }
        }
        exps.reverse();
        ops.reverse();
        while exps.len() > 1 {
            let exp1 = exps.pop().unwrap();
            let exp2 = exps.pop().unwrap();
            let op = ops.pop().unwrap();
            let res = match op {
                Op::Le => self.cmp_le(exp1, exp2),
                Op::Ge => self.cmp_ge(exp1, exp2),
                Op::Lt => self.cmp_lt(exp1, exp2),
                Op::Gt => self.cmp_gt(exp1, exp2),
            };
            let res = self.i1_to_i32(res);
            exps.push(res);
        }
        exps.pop().unwrap()
    }

    fn visit_eq_exp(&mut self, pair: Pair<Rule>) -> IntValue<'ctx> {
        enum Op {
            Eq,
            Neq,
        }
        let mut exps: Vec<IntValue> = Vec::new();
        let mut ops: Vec<Op> = Vec::new();
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::rel_exp => exps.push(self.visit_rel_exp(pair)),
                Rule::eq => ops.push(Op::Eq),
                Rule::neq => ops.push(Op::Neq),
                _ => unreachable!(),
            }
        }
        exps.reverse();
        ops.reverse();
        while exps.len() > 1 {
            let exp1 = exps.pop().unwrap();
            let exp2 = exps.pop().unwrap();
            let op = ops.pop().unwrap();
            let res = match op {
                Op::Eq => self.cmp_eq(exp1, exp2),
                Op::Neq => self.cmp_ne(exp1, exp2),
            };
            let res = self.i1_to_i32(res);
            exps.push(res);
        }
        exps.pop().unwrap()
    }

    fn visit_l_and_exp(
        &mut self,
        pair: Pair<Rule>,
        true_block: BasicBlock<'ctx>,
        false_block: BasicBlock<'ctx>,
    ) {
        let mut exps: Vec<Pair<Rule>> = Vec::new();
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::eq_exp => exps.push(pair),
                Rule::and => (),
                _ => unreachable!(),
            }
        }
        exps.reverse();
        let zero = self.get_const_val(0);
        while exps.len() > 1 {
            let val = self.visit_eq_exp(exps.pop().unwrap());
            let res = self.cmp_ne(zero, val);
            let function = self.current_function();
            let true_block = self.context.append_basic_block(function, "cond_true_");
            self.cond_br(res, true_block, false_block);
            self.builder.position_at_end(true_block);
        }
        let val = self.visit_eq_exp(exps.pop().unwrap());
        let res = self.cmp_ne(zero, val);
        self.cond_br(res, true_block, false_block);
    }

    fn visit_l_or_exp(
        &mut self,
        pair: Pair<Rule>,
        true_block: BasicBlock<'ctx>,
        false_block: BasicBlock<'ctx>,
    ) {
        let mut exps: Vec<Pair<Rule>> = Vec::new();
        for pair in pair.into_inner() {
            match pair.as_rule() {
                Rule::l_and_exp => exps.push(pair),
                Rule::or => (),
                _ => unreachable!(),
            }
        }
        exps.reverse();
        while exps.len() > 1 {
            let function = self.current_function();
            let false_block = self.context.append_basic_block(function, "cond_false_");
            self.visit_l_and_exp(exps.pop().unwrap(), true_block, false_block);
            self.builder.position_at_end(false_block);
        }
        self.visit_l_and_exp(exps.pop().unwrap(), true_block, false_block);
    }

    fn visit_const_exp(&mut self, pair: Pair<Rule>) -> ExpVal<'ctx> {
        let pair = pair.into_inner().next().unwrap();
        match pair.as_rule() {
            Rule::add_exp => self.visit_add_exp(pair),
            _ => unreachable!(),
        }
    }
}

#[derive(Clone)]
enum ExpVal<'ctx> {
    Num(i32),
    Val(IntValue<'ctx>),
}

pub fn ir_gen(input: &str, outfile: &str) {
    let mut pair = SysYParser::parse(Rule::program, input).unwrap();
    let pair = pair.next().unwrap();
    let context = Context::create();
    let mut generator = IRGenerator::new(&context, "module");
    generator.visit_program(pair);
    generator.module.print_to_file(outfile).unwrap();
}

use std::num::ParseIntError;
fn parse_int(s: &str) -> Result<i32, ParseIntError> {
    let s = s.trim();

    if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
        return i32::from_str_radix(hex, 16);
    } else if let Some(bin) = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")) {
        return i32::from_str_radix(bin, 2);
    } else if let Some(oct) = s.strip_prefix("0") {
        if oct.is_empty() {
            return Ok(0);
        }
        return i32::from_str_radix(oct, 8);
    }
    s.parse::<i32>()
}
